package tle.mock;

import java.io.IOException;

import net.sf.cglib.asm.Attribute;
import net.sf.cglib.asm.ClassAdapter;
import net.sf.cglib.asm.ClassReader;
import net.sf.cglib.asm.ClassVisitor;
import net.sf.cglib.asm.ClassWriter;
import net.sf.cglib.asm.CodeAdapter;
import net.sf.cglib.asm.CodeVisitor;
import net.sf.cglib.asm.Constants;

public class ProxyClassLoader extends ClassLoader {

	private String sourceClassName;
	private String replacedClassName;

	public ProxyClassLoader(ClassLoader parent, String sourceClassName,
			String replacedClassName) {
		super(parent);
		this.sourceClassName = sourceClassName;
		this.replacedClassName = replacedClassName;
	}

	public Class loadClass(String name) throws ClassNotFoundException {
		if (sourceClassName.equals(name)) {
			try {
				byte[] bytecode = transformClass(name);
				if (name.equals(sourceClassName)) {
					name = replacedClassName;
				}
				return super.defineClass(name, bytecode, 0, bytecode.length);

			} catch (IOException ex) {
				throw new ClassNotFoundException(
						"Load error: " + ex.toString(), ex);

			}
		} else {
			return super.loadClass(name);
		}
	}

	private byte[] transformClass(String className) throws IOException {
		ClassWriter cw = new ClassWriter(true, true);
		CustomClassVisitor cv = new CustomClassVisitor(cw, sourceClassName
				.replace(".", "/"), replacedClassName.replace(".", "/"));
		ClassReader cr = new ClassReader(getClass().getResourceAsStream(
				"/" + className.replace('.', '/') + ".class"));
		cr.accept(cv, false);
		return cw.toByteArray();
	}

	private class CustomClassVisitor extends ClassAdapter {
		private String sourceOwner;
		private String replacedOwner;

		public CustomClassVisitor(ClassVisitor classVisitor,
				String sourceOwner, String replacedOwner) {
			super(classVisitor);
			this.sourceOwner = sourceOwner;
			this.replacedOwner = replacedOwner;
		}

		public void visit(int version, int access, String name,
				String superName, String[] interfaces, String sourceFile) {
			name = name.equals(sourceOwner) ? replacedOwner : name;
			access = 33;
			super.visit(version, access, name, superName, interfaces,
					sourceFile);
		}

		public void visitField(int access, String name, String desc,
				Object value, Attribute attrs) {
			if (!name.equals("this$0")) {
				super.visitField(access, name, desc, value, attrs);
			}
		}

		public void visitInnerClass(String name, String outerName,
				String innerName, int access) {
			super.visitInnerClass(name, outerName, innerName, access);
		}

		public CodeVisitor visitMethod(int access, String name, String desc,
				String[] exceptions, Attribute attr) {
			if (name.equals("<init>") && sourceOwner.indexOf("$") > -1) {
				desc = "()V";
				access = Constants.ACC_PUBLIC;
			}
			CodeVisitor codeVisitor = super.visitMethod(access, name, desc,
					exceptions, attr);
			return new CustomCodeVisitor(codeVisitor, sourceOwner,
					replacedOwner, name.equals("<init>"));
		}
	}

	private class CustomCodeVisitor extends CodeAdapter {

		private String sourceOwner;
		private String replacedOwner;

		private boolean initVisitor;

		public CustomCodeVisitor(CodeVisitor origin, String sourceOwner,
				String replacedOwner, boolean initVisitor) {
			super(origin);
			this.sourceOwner = sourceOwner;
			this.replacedOwner = replacedOwner;
			this.initVisitor = initVisitor;
		}

		public void visitFieldInsn(int opcode, String owner, String name,
				String desc) {
			owner = owner.equals(sourceOwner) ? replacedOwner : owner;
			if (!name.equals("this$0")) {
				super.visitFieldInsn(opcode, owner, name, desc);
			}
		}

		public void visitMethodInsn(int opcode, String owner, String name,
				String desc) {
			owner = owner.equals(sourceOwner) ? replacedOwner : owner;
			super.visitMethodInsn(opcode, owner, name, desc);
		}

		public void visitVarInsn(int opcode, int var) {
			if (!(initVisitor && var == 1))
				super.visitVarInsn(opcode, var);
		}
	}

}
